<?php
SPRPC_Core::import('SPRPC_Handler_Base');
SPRPC_Core::import('iservice');
/**
 * SPRPC server handler
 * @static
 * @author bbqq
 * @copyright 2010 qstufie.com
 * @package com.sprpc.handler
 */
class SPRPC_Handler_Server extends SPRPC_Handler_Base
{
    /**
     * registered classes
     * use this to contain all the service that may have methods registered.
     * @var array
     */
    protected $_classes = array();


    /**
     * all available clients that are active
     * [token] = [id]
     * @var array
     */
    protected $_clients = array();


    /**
     * construct with clients so site id can be figured out...
     * @return void
     */
    public function __construct($clients = array())
    {
        // parse xml and see what happens
        $data = file_get_contents('php://input');
        if (empty($data)) {
            exit('NO INPUT');
        }

        if (empty($clients)) {
            $clients = SPRPC_Utility_Config::get('clients');
            if (empty($clients)) {
                throw new exception('CLIENTS NOT DEFINED', 1);
            }
        }

        // clients array
        $this->_clients = $clients;

        // validate xml...
        try {
            $this->_parse($data);
        } catch (Exception $e) {
            $e = new Exception($e->getMessage() . "\n\nPOST::[\n$data\n]", 100);
            $this->_error($e);
        }

    }// end __construct


    /**
     * server starts listenning
     */
    public function listen()
    {
        // first of all, get the right site id out to validate...
        foreach ($this->_clients as $id => $token) {
            if ($this->_mkchecksum($this->_payload, $token) == $this->_checksum) {
                $clientId = $id;
                $this->_token = $token;
                // do the rest here as well.
                $funcs = $this->_extract();
                try {
                    $payload = $this->_call($funcs['method'], $funcs['args'], $clientId);
                    // make flattened call back...
                    echo $this->_compose('response', $payload);
                    // all good.
                    return true;
                } catch (Exception $e) {
                    $this->_error($e);
                }
            }
        }

        // otherwise, bad!
        $e = new Exception('Permission denied', 503);
        $this->_error($e);

    }// end listen


    /**
     * register a new class that handles the service
     * all methods must be unique per class registered
     * you can only register 1 class with 1 method, not two
     * @param class $class
     * @param string/array $methods list of methods, one or many
     * @return $this
     */
    public function register(IService $class, $methods)
    {
        // the key is, use function to register class, instead of class to register function
        // for faster search
        if (is_array($methods)) {
            foreach ($methods as $method) {
                $this->_classes[(string) $method] = $class;
            }
        } else {
            $this->_classes[(string) $methods] = $class;
        }

        return $this;

    }// end registerClass


    /**
     * take method calls
     * used in local (server)
     * @param $method the method to call
     * @param $args the params
     * @param $clientId the site id
     * @return mixed the actual class call.
     */
    public function _call($method, $args, $clientId)
    {
        // special method check...
        // if method exists in self... and named as _server_
        if (method_exists($this, $method) && substr($method, 0, 8) == '_server_') {
            try {
                return call_user_func_array(array($this, $method), $args);
            } catch (Exception $e) {
                $this->_error($e);
            }
        }

        // use smart choice, first in first out
        if (isset($this->_classes[$method])) {
            $class = $this->_classes[$method];
            $class->setClientId($clientId);
            try {
                return call_user_func_array(array($class, $method), $args);
            } catch (Exception $e) {
                $this->_error($e);
            }
        }

        // otherwise, nothing is there!
        $e = new Exception("Method Not Registered [$method]", 100);
        $this->_error($e);

    }// end __call

    /*-----------------------------------[native server functions (can be delegate too)]-----------------------------------*/

    /**
     * explain a function
     * @param str $method the method to explain
     * @return str the explanation
     */
    protected function _server_explain($method)
    {
        // first of all, func must be in...
        if (isset($this->_classes[$method])) {
            // good...
            $class = get_class($this->_classes[$method]);
            // use reflection to explain...
            $ref = new ReflectionMethod($class, $method);
            // start checking...
            $details = $ref->getDocComment() . "\n" .
                       'CLASS:  ' . $class . "\n" .
                       'METHOD: ' . $method  . "\n";
            $params = $ref->getParameters();
            if (is_array($params)) {
                $details .= "PARAMS: \n";
                foreach ($params as $param) {
                    if ($param instanceof ReflectionParameter) {
                        $details .= '       - ' . $param->__toString() . "\n";
                    }
                }
            }
            return $details;
        }

        // otherwise, nothing is there!
        throw new Exception("Method Not Registered [$method]", 100);

    }// end _server_explain

} // end SPRPC_Handler_Server
?>